package cn.ms.light;

import static cn.ms.light.core.Dispatch.using;
import static cn.ms.light.core.Dispatch.withParams;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.alibaba.fastjson.JSON;
import com.thoughtworks.xstream.XStream;

import cn.ms.light.annotation.Ctrl;
import cn.ms.light.annotation.Handler;
import cn.ms.light.common.NetUtils;
import cn.ms.light.common.ScanPackage;
import cn.ms.light.common.StringUtils;
import cn.ms.light.core.Route;
import cn.ms.light.core.Router;
import cn.ms.light.handler.Handler404;
import cn.ms.light.monitor.HttpServerListener;
import cn.ms.light.type.DataType;
import cn.ms.light.type.ReqType;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.reactivex.netty.RxNetty;
import io.reactivex.netty.protocol.http.server.HttpServer;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.file.ClassPathFileRequestHandler;
import rx.Observable;

public enum Light {

	INSTANCE;
	
	private HttpServer<ByteBuf, ByteBuf> httpServer;
	private XStream xstream=new XStream();
	private LightEnvironment environment=new LightEnvironment(Light.class);
	
	public void start(int port, String pack){
		start(port, true, "page", pack);
	}
	
	public void start(int port, boolean wait, String path, String pack) {
		Set<Class<?>> set=ScanPackage.getClasses(pack);
		Router<ByteBuf, ByteBuf> router=new Router<ByteBuf, ByteBuf>();
		for (final Class<?> clazz:set) {//扫描控制器
			Ctrl ctrl=clazz.getAnnotation(Ctrl.class);
			if(ctrl!=null){
				Method[] methods=clazz.getDeclaredMethods();
				for (final Method method:methods) {//扫描action
					final Handler handler=method.getAnnotation(Handler.class);
					if(handler!=null){
						String urlPath=ctrl.value()+handler.value();
						try {
							final Object obj=clazz.newInstance();
							try {
								//组建action
								Route<ByteBuf, ByteBuf> tempRoute =new Route<ByteBuf, ByteBuf>() {
									public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
										return handle(new HashMap<String, String>(), request, response);
									}
									public Observable<Void> handle(Map<String, String> params, HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
										response = doHandler(handler, obj, method, params, request, response);
										return response.close();
									}
								};
								router=buildRouter(urlPath, handler.reqType(), tempRoute, router);
							} catch (Exception e) {
								e.printStackTrace();
							}
						} catch (InstantiationException | IllegalAccessException e) {
							throw new RuntimeException(String.format("The controller[%s] must contain a default constructor.", clazz.getName()));
						}
					}
				}
			}
		}
		
		//校验根节点
		if(StringUtils.isBlank(path)){
			path="page";
		}
		
		//资源URL
		router=router.GET("/:*", new ClassPathFileRequestHandler(path));
		//404资源
		router=router.notFound(new Handler404());
		httpServer=RxNetty.createHttpServer(port, using(router));
		httpServer.subscribe(new HttpServerListener());
		
		environment.getServerAddress(Light.class, new String[]{String.valueOf(httpServer.getServerPort()),NetUtils.getLocalIp()});
		
		if(wait){
			httpServer.startAndWait();	
		}else{
			httpServer.start();
		}
	}
	
	private HttpServerResponse<ByteBuf> doHandler(Handler handler,Object obj, Method method,Map<String, String> params, HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
		try {
			ReqType reqType=handler.reqType();
			if(reqType.equals(ReqType.ALL)||reqType.name().equals(request.getHttpMethod().name())){
				//调用Action
				Object objResult=buildParamsInvoke(obj, method, params, request, response);
				
				if(handler.dataType().equals(DataType.JSON)){
					response.getHeaders().setHeader("Content-Type", String.format("application/json;charset=%s", handler.charset()));
					response.writeString(JSON.toJSONString(objResult));
				}else if(handler.dataType().equals(DataType.XML)){
					response.getHeaders().setHeader("Content-Type", String.format("application/xml;charset=%s",handler.charset()));
					response.writeString(xstream.toXML(objResult));
				}else if(handler.dataType().equals(DataType.TEXT)){
					response.getHeaders().setHeader("Content-Type", String.format("text/plain;charset=%s",handler.charset()));
					response.writeString(String.valueOf(objResult));
				}else {
					response.getHeaders().setHeader("Content-Type", handler.contentType());
					response.writeBytes(toByteArray(objResult));
				}
				
				response.setStatus(HttpResponseStatus.OK);
			}else{
				response.writeString("Type of illegal request, please check the request source type (ReqType).");
				response.setStatus(HttpResponseStatus.NOT_ACCEPTABLE);
			}
			return response;
		} catch (Exception e) {
			response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
			response.writeString(e.getMessage());
			e.printStackTrace();
			
			return response;			
		}
	}
	
	/**  
     * 对象转数组  
     * @param obj  
     * @return  
     */  
	private byte[] toByteArray (Object obj) {      
        byte[] bytes = null;      
        ByteArrayOutputStream bos = new ByteArrayOutputStream();      
        try {        
            ObjectOutputStream oos = new ObjectOutputStream(bos);         
            oos.writeObject(obj);        
            oos.flush();         
            bytes = bos.toByteArray ();      
            oos.close();         
            bos.close();        
        } catch (IOException ex) {        
            ex.printStackTrace();   
        }      
        return bytes;    
    }   
	
	/**
	 * 构建Router
	 * 
	 * @param path
	 * @param reqType
	 * @param tempRoute
	 * @param router
	 * @return
	 */
	private Router<ByteBuf, ByteBuf> buildRouter(String path, ReqType reqType, Route<ByteBuf, ByteBuf> tempRoute, Router<ByteBuf, ByteBuf> router) {
		switch (reqType) {
		case ALL:
			return router.GET(path, withParams(tempRoute));
		case CONNECT:
			return router.CONNECT(path, withParams(tempRoute));
		case DELETE:
			return router.DELETE(path, withParams(tempRoute));
		case GET:
			return router.GET(path, withParams(tempRoute));
		case HEAD:
			return router.HEAD(path, withParams(tempRoute));
		case OPTIONS:
			return router.OPTIONS(path, withParams(tempRoute));
		case PATCH:
			return router.PATCH(path, withParams(tempRoute));
		case POST:
			return router.POST(path, withParams(tempRoute));
		case PUT:
			return router.PUT(path, withParams(tempRoute));
		case TRACE:
			return router.TRACE(path, withParams(tempRoute));
		default:
			throw new RuntimeException();
		}
	}
	
	/**
	 * 调用Action
	 * 
	 * @param obj
	 * @param method
	 * @param params
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	private Object buildParamsInvoke(Object obj, Method method, Map<String, String> params, 
			HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) throws Exception {
		Class<?>[] parameterTypes=method.getParameterTypes();
		if(parameterTypes==null||parameterTypes.length==0){
			return method.invoke(obj);
		}
		
		Object[] paramsVal=new Object[parameterTypes.length];
		int kno=0;
		for (Class<?> parameterType:parameterTypes) {
			if(parameterType.getName().equals(request.getClass().getName())){
				paramsVal[kno]=request;
			}else if(parameterType.getName().equals(response.getClass().getName())){
				paramsVal[kno]=response;
			}else if(parameterType.getName().equals(Map.class.getName())){
				paramsVal[kno]=params;
			} else {
				if(parameterType.getName().equals("int")|| parameterType.getName().equals("long")||
						parameterType.getName().equals("byte")|| parameterType.getName().equals("short")||
						parameterType.getName().equals("float")|| parameterType.getName().equals("double")||
						parameterType.getName().equals("char")){
					paramsVal[kno]=0;
				}else if(parameterType.getName().equals("boolean")){
					paramsVal[kno]=false;
				}else{
					paramsVal[kno]=null;					
				}
			}
			
			kno++;
		}
		
		return method.invoke(obj, paramsVal);
	}
	
	public void destroy(){
		
	}
	
}
